


local Element = import("./Element.lua");
local KeyBoard = import("../KeyBoard.lua");

local Input = commonlib.inherit(Element);

Input:Property("ShowCursor", false, "IsShowCursor");
Input:Property("DefaultStyle", "box-sizing: border-box; display: inline-block; border-width: 1px; border-color: #cccccc; color: #000000; height: 30px; line-height: 30px; width: 120px; padding-left: 4px; padding-right: 4px; background-color: #ffffff;");

local CursorShowHideMaxTickCount = 30;

function Input:ctor()
    self:SetName("Input");

    self.text = commonlib.ustring();

    self:Reset();
end

function Input:Reset()
    self.cursorShowHideTickCount = 0;
    self.cursorAt = 1;                                 -- 光标索引位置 占据下一个输入位置
    self.cursorX = 0;                                  -- 光标的位置
    self.offsetX = 0;                                  -- X 轴偏移量
    self.undoCmds = {};                                -- 撤销命令
    self.redoCmds = {};                                -- 重做命令
    self.selectStartAt, self.selectEndAt = 0, 0;       -- 文本选择
    self.selectStartX, self.selectEndX = 0, 0;
end

function Input:Init(xmlnode, window, parent)
    self:InitElement(xmlnode, window, parent);

    self:OnAttrValueChange("value", self:GetAttrStringValue("value", ""));

    -- if (self:GetAttrBoolValue("autofocus")) then self:FocusIn() end

    return self;
end

function Input:OnAttrValueChange(attrName, attrValue, oldAttrValue)
    Input._super.OnAttrValueChange(self, attrName, attrValue, oldAttrValue);
    if (attrName == "value") then self:SetValue(attrValue) end
end

function Input:SetValue(value)
    value = tostring(value or "");
    if (value == self.text:tostring()) then return end
    self.text = commonlib.ustring(value);
    self:Reset();
end

function Input:GetValue()
    return self.text:tostring();
end

function Input:GetText()
    return self.text;
end

-- 是否选择
function Input:IsSelected()
    return self.selectStartAt > 0 and self.selectEndAt > 0 and self.selectStartAt ~= self.selectEndAt;  -- [selectStartAt, selectEndAt)
end

-- 获取选择的文本
function Input:GetSelectedText()
    return self.text:sub(self:GetSelected()):tostring();
end

-- 获取选择
function Input:GetSelected()
    if (not self:IsSelected()) then return 0, 0 end
    if (self.selectStartAt < self.selectEndAt) then return self.selectStartAt, self.selectEndAt end
    return self.selectEndAt, self.selectStartAt;
end

-- 清除选择
function Input:ClearSelected()
    self.selectStartAt, self.selectEndAt = 0, 0;
    self:AdjustSelect();
end

function Input:IsReadOnly()
    return self:GetAttrBoolValue("readonly");
end

function Input:handleReturn()
    self:CallAttrFunction("onkeydown.enter", nil, self:GetValue());
    self:FocusOut();
end

function Input:handleEscape()
    self:FocusOut();
end

function Input:handleBackspace()
    if (self:IsSelected()) then
        self:DeleteSelected();
    else
        self:DeleteTextCmd(self.cursorAt - 1, 1);
    end
end

function Input:handleDelete()
    if (self:IsSelected()) then
        self:DeleteSelected();
    else
        self:DeleteTextCmd(self.cursorAt, 1);
    end
end

function Input:handleUndo()
    if (#self.undoCmds == 0) then return end
    local len = #self.undoCmds;
    local cmd = self.undoCmds[len];
    table.remove(self.undoCmds, len);
    table.insert(self.redoCmds, cmd);
    if (cmd.action == "insert") then
        self:DeleteText(cmd.startAt, cmd.endAt);
    elseif (cmd.action == "delete") then
        self:InsertText(cmd.startAt, cmd.endAt, cmd.text);
    end
end

function Input:handleRedo()
    if (#self.redoCmds == 0) then return end
    local len = #self.redoCmds;
    local cmd = self.redoCmds[len];
    table.remove(self.redoCmds, len);
    table.insert(self.undoCmds, cmd);
    if (cmd.action == "insert") then
        self:InsertText(cmd.startAt, cmd.endAt, cmd.text);
    elseif (cmd.action == "delete") then
        self:DeleteText(cmd.startAt, cmd.endAt);
    end
end

function Input:AdjustSelect()
    local selectStartAt, selectEndAt = self:GetSelected();
    self.selectStartX = selectStartAt <= 1 and 0 or self:GetTextWidth(self.text:sub(1, selectStartAt - 1):tostring());
    self.selectEndX = selectEndAt <= 1 and 0 or self:GetTextWidth(self.text:sub(1, selectEndAt - 1):tostring());
end

function Input:HandleSelectAll()
    self.selectStartAt, self.selectEndAt = 1, self.text:len() + 1;
    self:AdjustSelect();
end

function Input:handleSelectWorld()
    -- local beginPos, endPos = self:GetText():wordPosition(self.cursorAt);
    -- self.selectStartAt, self.selectEndAt = beginPos + 1, endPos;
end

function Input:handleCopy()
    if (not self:IsSelected()) then return end
    local selectedText = self:GetSelectedText();
    commonlib.SetClipboardText(selectedText);
end

function Input:handleCut()
    self:handleCopy();
    self:DeleteSelected();
end

function Input:handlePaste()
    local text = commonlib.GetClipboardText();
    self:InsertTextCmd(clip)
end

function Input:handleMoveHome(event, bselected)
    if (bselected) then
        self.selectStartAt = 1;
        self.selectEndAt = self.cursorAt;
        self:AdjustSelect();
    else
        self:AdjustCursor(- self.cursorAt + 1);
    end
    self:ResetCursor();
end

function Input:handleMoveEnd(event, bselected)
    if (bselected) then
        self.selectStartAt = self.cursorAt;
        self.selectEndAt = self:GetText():len();
        self:AdjustSelect();
    else
        self:AdjustCursor(self:GetText():len() + 1 - self.cursorAt);
    end
    self:ResetCursor();
end

function Input:handleMoveToNextChar()
    self:ClearSelected();
    self:AdjustCursor(1);
    self:ResetCursor();
end

function Input:handleMoveToPrevChar()
    self:ClearSelected();
    self:AdjustCursor(-1);
    self:ResetCursor();
end

function Input:handleSelectNextChar()
    self.selectStartAt = self.selectStartAt == 0 and self.cursorAt or self.selectStartAt;
    self.selectEndAt = self.selectEndAt == 0 and self.cursorAt or self.selectEndAt;
    self.selectEndAt = self.selectEndAt + 1;
    self.selectEndAt = math.max(math.min(self.selectEndAt, self.text:len() + 1), 1);
    self:AdjustSelect();
    self:AdjustCursor(1);
    -- print("Input:handleSelectNextChar", self.selectStartAt, self.selectEndAt, self.selectStartX, self.selectEndX)
end

function Input:handleSelectPrevChar()
    self.selectStartAt = self.selectStartAt == 0 and self.cursorAt or self.selectStartAt;
    self.selectEndAt = self.selectEndAt == 0 and self.cursorAt or self.selectEndAt;
    self.selectEndAt = self.selectEndAt - 1;
    self.selectEndAt = math.max(math.min(self.selectEndAt, self.text:len() + 1), 1);
    self:AdjustSelect();
    self:AdjustCursor(-1);
    -- print("Input:handleSelectPrevChar", self.selectStartAt, self.selectEndAt, self.selectStartX, self.selectEndX)
end

function Input:handleMoveToNextWord()
end
function Input:handleMoveToPrevWord()
end
function Input:handleSelectNextWord()
end
function Input:handleSelectPrevWord()
end
function Input:OnKeyDown(event)
    if (not self:IsFocus() or self:IsDisabled() or self:IsReadOnly()) then return end

	if (KeyBoard:IsKeyPressed(KeyBoard.VK_RETURN)) then self:handleReturn() 
	elseif (KeyBoard:IsKeyPressed(KeyBoard.VK_ESCAPE)) then self:handleEscape()
	elseif (KeyBoard:IsKeyPressed(KeyBoard.VK_BACKSPACE)) then self:handleBackspace()
    elseif (KeyBoard:IsKeyPressed(KeyBoard.VK_DELETE)) then self:handleDelete()
	elseif (KeyBoard:IsCtrlKeyPressed() and KeyBoard:IsKeyPressed(KeyBoard.VK_Z)) then self:handleUndo()
	elseif (KeyBoard:IsCtrlKeyPressed() and KeyBoard:IsKeyPressed(KeyBoard.VK_Y)) then self:handleRedo()
	elseif (KeyBoard:IsCtrlKeyPressed() and KeyBoard:IsKeyPressed(KeyBoard.VK_A)) then self:HandleSelectAll()
	elseif (KeyBoard:IsCtrlKeyPressed() and KeyBoard:IsKeyPressed(KeyBoard.VK_C)) then self:handleCopy()
	elseif (KeyBoard:IsCtrlKeyPressed() and KeyBoard:IsKeyPressed(KeyBoard.VK_V)) then self:handlePaste()
	elseif (KeyBoard:IsCtrlKeyPressed() and KeyBoard:IsKeyPressed(KeyBoard.VK_X)) then self:handleCut()
	elseif (KeyBoard:IsShiftKeyPressed() and KeyBoard:IsKeyPressed(KeyBoard.VK_RIGHT)) then self:handleSelectNextChar(event)
	elseif (KeyBoard:IsShiftKeyPressed() and KeyBoard:IsKeyPressed(KeyBoard.VK_LEFT)) then self:handleSelectPrevChar(event)
    elseif (KeyBoard:IsKeyPressed(KeyBoard.VK_HOME)) then self:handleMoveHome()
    elseif (KeyBoard:IsKeyPressed(KeyBoard.VK_END)) then self:handleMoveEnd()
	elseif (KeyBoard:IsKeyPressed(KeyBoard.VK_RIGHT)) then self:handleMoveToNextChar(event)
	elseif (KeyBoard:IsKeyPressed(KeyBoard.VK_LEFT)) then self:handleMoveToPrevChar(event)
    else
	end
end

function Input:OnChar(event)
    if (not self:IsFocus() or self:IsDisabled() or self:IsReadOnly()) then return end

    -- 输入串
    local char = event:GetChar();

    -- 忽略控制字符
    local charcode = string.byte(char, 1);
    -- print("Input:OnChar", char, string.len(char), charcode);

    if(charcode <= 31) then return end

    -- 添加新文本
    self:InsertTextCmd(char);
end

-- 检测输入是否合法
function Input:CheckInputText(text)
    local inputType = self:GetAttrStringValue("type", "text");
    if (inputType == "number") then
        return string.match(text, "^[%-%d%.]*$");
    end
    return true;
end

function Input:InsertTextCmd(text, startAt)
    if (not text or text == "") then return end
    if (not self:CheckInputText(text)) then return end
    -- 先删除已选择的文本
    if (self:IsSelected()) then 
        local selectStartAt = self:GetSelected();
        self:DeleteSelected();
        self.cursorAt = selectStartAt;
    end

    startAt = math.min(startAt or self.cursorAt, self:GetTextLength() + 1);
    local textLength = commonlib.ustring(text):len();
    local endAt = startAt + textLength - 1;
    table.insert(self.undoCmds, {startAt = startAt, endAt = endAt, action = "insert", text = text});
    -- print(string.format("InsertTextCmd before cursorAt = %s, startAt = %s, endAt = %s, text = %s", self.cursorAt, startAt, endAt, self:GetValue()));
    self:InsertText(startAt, endAt, text);
    -- print(string.format("InsertTextCmd after cursorAt = %s, startAt = %s, endAt = %s, text = %s", self.cursorAt, startAt, endAt, self:GetValue()));
end

function Input:InsertText(startAt, endAt, text)
    self.text:insert(startAt, text);
    self:OnChange();
    if (startAt <= self.cursorAt) then self:AdjustCursor(endAt - startAt + 1) end
end

function Input:DeleteSelected()
    if (not self:IsSelected()) then return end
    local selectStartAt, selectEndAt = self:GetSelected();
    self:DeleteTextCmd(selectStartAt, selectEndAt - selectStartAt + 1);
    self:ClearSelected();
end

function Input:DeleteTextCmd(startAt, count)
    if (not startAt or not count or count == 0) then return end
    local endAt = startAt + count - 1;
    if (endAt < startAt) then startAt, endAt = endAt, startAt end
    if (endAt < 1) then return end
    startAt = math.max(startAt, 1);
    table.insert(self.undoCmds, {startAt = startAt, endAt = endAt, action = "delete", text = self.text:sub(startAt, endAt)});
    -- commonlib.print_format("DeleteTextCmd before cursorAt = %s, startAt = %s, endAt = %s, text = %s", self.cursorAt, startAt, endAt, self:GetValue());
    self:DeleteText(startAt, endAt);
    -- commonlib.print_format("DeleteTextCmd after cursorAt = %s, startAt = %s, endAt = %s, text = %s", self.cursorAt, startAt, endAt, self:GetValue());
end

function Input:DeleteText(startAt, endAt)
    local count = endAt - startAt + 1;
    self.text:remove(startAt, count);
    if (self.cursorAt <= startAt) then
    elseif (self.cursorAt >= endAt) then self:AdjustCursor(-count)
    else self:AdjustCursor(startAt - self.cursorAt) end 
    self:OnChange();
end

function Input:GetTextLength()
    return self.text:len();
end

function Input:OnChange()
    self:GetAttr()["value"] = self:GetValue();
    self:CallAttrFunction("onchange", nil, self:GetValue());
end

-- 调整光标的位置, 调整前文本需完整, 因此添加需先添加后调整光标, 移除需先调整光标后移除
function Input:AdjustCursor(offset)
    -- commonlib.print_format("AdjustCursor Before cursorAt = %s, content_width = %s, cursorX = %s, offsetX = %s, text = %s", self.cursorAt, self.__content_width__, self.cursorX, self.offsetX, self.text:tostring());
    if (not offset or offset == 0 or not self.__content_width__) then return end

    -- 保存光标位置的正确性 调整 offset 
    local cursorAt, maxAt = self.cursorAt + offset, self.text:len() + 1;
    if (cursorAt > maxAt) then offset = maxAt - self.cursorAt end
    if (cursorAt < 1) then offset = 1 - self.cursorAt end
    if (offset == 0) then return end

    self.cursorAt = self.cursorAt + offset;
    self.cursorX = self:GetTextWidth(self.text:sub(1, self.cursorAt - 1):tostring());
    if (self.cursorX > (self.__content_width__ + self.offsetX)) then 
        self.offsetX = self.cursorX - self.__content_width__;
    elseif (self.cursorX < self.offsetX) then
        self.offsetX = self.cursorX;
    end
    self:OnCursorChange();
    -- commonlib.print_format("AdjustCursor After cursorAt = %s, content_width = %s, cursorX = %s, offsetX = %s, text = %s", self.cursorAt, self.__content_width__, self.cursorX, self.offsetX, self.text:tostring());
end

function Input:OnCursorChange()
    local x, y, w, h = self:GetPosition();
    x = x + self.cursorX - self.offsetX;
    local sx, sy = self:ElementXYToScreenXY(x, y);
    commonlib.SetTextInputPosition(sx, sy, w, h);
end

-- 绘制内容
function Input:RenderContent()
    local _x, _y, _w, _h = self:GetPosition();
    local x, y, w, h = self:GetContentPosition();

    self:Save();
    self:DrawRectangle(_x, _y, _w, _h, false, false);
    self:Clip();
    self:Translate(x, y);

    if (self:IsSelected()) then
        self:SetBrushColor(0x3390ffff);
        self:DrawRectangle(self.selectStartX - self.offsetX, 0, self.selectEndX - self.selectStartX, h);
    end

    local text = self.text:tostring();
    self:ApplyStyleFontToPaint();
    if (self:IsFocus() or text ~= "") then 
        -- text render
        self:SetFontColor(self.__style_color__);
        self:DrawText(-self.offsetX, 0, w, h, text);
    else 
        -- placeholder render
        self:SetFontColor(0xA8A8A8FF);
        self:DrawText(0, 0, w, h, self:GetAttrStringValue("placeholder", ""));        
    end

    -- cursor render 
    if (self:IsFocus()) then
        self.cursorShowHideTickCount = self.cursorShowHideTickCount + 1;
        if (self.cursorShowHideTickCount > CursorShowHideMaxTickCount) then 
            self.cursorShowHideTickCount = 0;
            self:SetShowCursor(not self:IsShowCursor());
        end
        if (self:IsShowCursor()) then
            self:SetPenColor(0xff);
            self:SetLineWidth(1);
            self:DrawLine(self.cursorX - self.offsetX - 1, 0 + 1, self.cursorX - self.offsetX - 1, h - 1);
        end
    end

    self:Translate(-x, -y);
    self:Restore();
end

function Input:ResetCursor()
    self.cursorShowHideTickCount = 0;
    self:SetShowCursor(true);
end

function Input:OnFocusIn()
    Input._super.OnFocusIn(self);
    self:ResetCursor();
    commonlib.StartTextInput();
end

function Input:OnFocusOut()
    Input._super.OnFocusOut(self);
    self:ClearSelected();
    commonlib.StopTextInput();
end

function Input:GetAtByPos(sx, sy)
    local x, y, w, h = self:GetContentPosition();
    local vx = sx - x;   -- 视图坐标
    local cursorX = vx + self.offsetX;
    local text = self:GetTextByWidth(self.text:tostring(), cursorX);
    local cursorAt = commonlib.ustring(text):len() + 1;
    local cursorX = self:GetTextWidth(text);
    -- commonlib.print_format("GetAtByPos, x = %s, cursorAt = %s, cursorX = %s", x, cursorAt, cursorX);
    return cursorAt, cursorX;
end

function Input:OnMouseDown(event)
    if (self:IsDisabled() or not event:IsLeftButton()) then return end
    if (not self:IsFocus()) then self:FocusIn() end
    
    local sx, sy = event:GetXY();
    if (event:IsTripleClick()) then
        self:HandleSelectAll();
    elseif (event:IsDoubleClick()) then
        self:handleSelectWorld();
    else
        self:ClearSelected();
        self.cursorAt, self.cursorX = self:GetAtByPos(sx, sy);
        self.mouseDown = true;
        self:CaptureMouse();
        self:OnCursorChange();
    end
    event:Accept();
end

function Input:OnMouseMove(event)
    if (not self.mouseDown) then return end
    local sx, sy = event:GetXY()
    if (not self:Contain(sx, sy) or not event:IsLeftButtonPressed()) then return end

    self.cursorAt, self.cursorX = self:GetAtByPos(sx, sy);
    self.selectStartAt = self.selectStartAt == 0 and self.cursorAt or self.selectStartAt;
    self.selectEndAt = self.cursorAt;
    self:AdjustSelect();
    event:Accept();
end

function Input:OnMouseUp(event)
    self:ReleaseMouse();
    self.mouseDown = false;
    if (not self:IsSelected()) then self:ClearSelected() end 
    event:Accept();
end


return Input;















